#pragma once

#ifndef __LIT__MESH__INCLUDED__HH__
#define __LIT__MESH__INCLUDED__HH__

#include "LitPrerequisites.h"
#include <OgreMesh.h>
#include <OgreSubMesh.h>
#include <OgreMeshManager.h>

namespace Lit
{
		class LIT_API MeshDeclaration
		{
		public:
			struct Vertex
			{
				Vertex(){
					position = Lit::Vec3::ZERO;
					normal = std::make_pair( Lit::Vec3::ZERO, false );
					tangent = std::make_pair( Lit::Vec3::ZERO, false );
				}
				Vertex( const Lit::Vec3 &pos, const Lit::Vec3& norm=Lit::Vec3::ZERO, const Lit::Vec3& tang=Lit::Vec3::ZERO)
				{
					position=pos;
					normal = std::make_pair( norm, norm!=Lit::Vec3::ZERO );
					tangent = std::make_pair( tang, tang!=Lit::Vec3::ZERO );
				}

				Lit::Vec3					position;
				std::pair<Lit::Vec3, bool>	normal;
				std::pair<Lit::Vec3, bool>	tangent;
			};
			struct Element
			{
				Element( Ogre::VertexElementType type_, Ogre::VertexElementSemantic semantic_ )
					: type(type_), semantic(semantic_){}
				Element( const Element& rk )
					: type(rk.type), semantic(rk.semantic), offset(rk.offset){}
				~Element(){}

				void setOffset( size_t off ){ offset = off; }
				size_t getTypeSize() const{ return Ogre::VertexElement::getTypeSize( type ); }

				Ogre::VertexElementType			type;
				Ogre::VertexElementSemantic		semantic;
				size_t							offset;
			};
			typedef std::vector<Element>	 ElementList;
			typedef std::vector<Vertex>		 VertexList;
			typedef std::vector<uint16_t>	 IndexList;

		private:
			ElementList m_list;
			VertexList  m_verts;
			IndexList	m_inds;
			size_t		m_offset;
			std::string	m_name;
		public:
			MeshDeclaration( const std::string &name = "" ) 
				: m_offset( 0 ), m_name( name )
			{}
			MeshDeclaration( const MeshDeclaration& rk ) 
				: m_verts( rk.m_verts ), m_name(rk.m_name), m_inds( rk.m_inds ), m_offset( rk.m_offset ), m_list( rk.m_list )
			{}
			~MeshDeclaration()
			{ cleanup(); }

			const std::string &getName() const{ return m_name; }

			void uploadElements(Ogre::VertexDeclaration *decl){
				if( !decl ) return;
				for( auto i=m_list.begin(); i!=m_list.end(); i++ )
					decl->addElement( 0, i->offset, i->type, i->semantic );
			}
			MeshDeclaration* addVertex( const Vertex& vert )
			{
				m_verts.push_back( vert );
				return this;
			}
			MeshDeclaration* addIndex( uint16_t id )
			{
				m_inds.push_back( id );
				return this;
			}
			MeshDeclaration* addElement( const Element& elem )
			{
				Element e( elem );
				e.setOffset( m_offset );
				m_list.push_back( e );
				m_offset += m_list.back().getTypeSize();
				return this;
			}
			void cleanup()
			{
				m_list.clear();
				m_inds.clear();
				m_verts.clear();
			}
			size_t getNumElements() const{ return m_list.size(); }
			bool hasElements() const{ return !m_list.empty(); }
			
			size_t getNumVertices() const{ return m_verts.size(); }
			bool hasVertices() const{ return !m_verts.empty(); }

			size_t getNumIndices() const{ return m_inds.size(); }
			bool hasIndices() const{ return !m_inds.empty(); }
			
			uint16_t& getIndex( size_t id ){ assert(id<m_list.size()); return m_inds.at(id); }
			const uint16_t& getIndex( size_t id ) const{ assert(id<m_list.size()); return m_inds.at(id); }

			Element& getElement( size_t id ){ assert(id<m_list.size()); return m_list.at(id); }
			const Element& getElement( size_t id ) const{ assert(id<m_list.size()); return m_list.at(id); }

			Vertex& getVertex( size_t id ){ assert(id<m_list.size()); return m_verts.at(id); }
			const Vertex& getVertex( size_t id ) const{ assert(id<m_list.size()); return m_verts.at(id); }
			
			ElementList& getElements(){ return m_list; }
			VertexList& getVertices(){ return m_verts; }
			IndexList& getIndices(){ return m_inds; }
			const ElementList& getElements()const { return m_list; }
			const VertexList& getVertices()const { return m_verts; }
			const IndexList& getIndices()const { return m_inds; }

			size_t getOffset() const
			{
				return m_offset;
			}
		};
		typedef Lit::RefPtr<MeshDeclaration> MeshDeclarationPtr;

		class LIT_API MeshBuilder
		{
		public:
			MeshBuilder( )
			{}
			virtual ~MeshBuilder(){ }
			
			static Ogre::MeshPtr create( MeshDeclarationPtr decl, const std::string &res_grp=Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME )
			{
				if( !decl ) return Ogre::MeshPtr();
				Ogre::MeshPtr mMesh = Ogre::MeshManager::getSingleton().createManual(decl->getName(), res_grp);
				/* create the vertex data structure */
				mMesh->sharedVertexData = new Ogre::VertexData;
				mMesh->sharedVertexData->vertexCount = decl->getNumVertices();

				/* declare how the vertices will be represented */
				decl->uploadElements(mMesh->sharedVertexData->vertexDeclaration);

				/* create the vertex buffer */
				Ogre::HardwareVertexBufferSharedPtr vertexBuffer = Ogre::HardwareBufferManager::getSingleton().
					createVertexBuffer(
						decl->getOffset(), 
						mMesh->sharedVertexData->vertexCount, 
						Ogre::HardwareBuffer::HBU_STATIC
					);

				/* lock the buffer so we can get exclusive access to its data */
				float *vertices = static_cast<float *>(vertexBuffer->lock(Ogre::HardwareBuffer::HBL_NORMAL));
				Lit::Vec3 min(999999.99f), max(-999999.99f);
				size_t id = 0;
				for( auto i=decl->getVertices().begin(); i!=decl->getVertices().end(); i++ )
				{
					min = std::min( min, i->position );
					max = std::max( max, i->position );

					vertices[id] = i->position.x; 
					vertices[id+1] = i->position.y; 
					vertices[id+2] = i->position.z;
					id+=3;
					if( i->normal.second )
					{
						vertices[id] = i->normal.first.x; 
						vertices[id+1] = i->normal.first.y; 
						vertices[id+2] = i->normal.first.z;
						id+=3;
					}
					if( i->tangent.second )
					{
						vertices[id] = i->tangent.first.x; 
						vertices[id+1] = i->tangent.first.y; 
						vertices[id+2] = i->tangent.first.z;
						id+=3;
					}
					id++;
				}
				/* unlock the buffer */
				vertexBuffer->unlock();

				/* create the index buffer */
				Ogre::HardwareIndexBufferSharedPtr indexBuffer(Ogre::HardwareBufferManager::getSingleton().
					createIndexBuffer(Ogre::HardwareIndexBuffer::IT_16BIT, mMesh->sharedVertexData->vertexCount, Ogre::HardwareBuffer::HBU_STATIC));

				/* lock the buffer so we can get exclusive access to its data */
				uint16_t *indices = static_cast<uint16_t *>(indexBuffer->lock(Ogre::HardwareBuffer::HBL_NORMAL));
				
				id = 0;
				for( auto i=decl->getIndices().begin(); i!=decl->getIndices().end(); i++ )
				{
					/* define our triangle */
					indices[id] = id; id++;
				}
				/* unlock the buffer */
				indexBuffer->unlock();

				/* attach the buffers to the mesh */
				mMesh->sharedVertexData->vertexBufferBinding->setBinding(0, vertexBuffer);
				Ogre::SubMesh* subMesh = mMesh->createSubMesh();
				subMesh->useSharedVertices = true;
				subMesh->indexData->indexBuffer = indexBuffer;
				subMesh->indexData->indexCount = mMesh->sharedVertexData->vertexCount;
				subMesh->indexData->indexStart = 0;

				/* set the bounds of the mesh */
				mMesh->_setBounds(Ogre::AxisAlignedBox(min, max));

				/* notify the mesh that we're all ready */
				mMesh->load();

				decl.destroy();

				return mMesh;
			}
		};
};

#endif